using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using HslCommunication.BasicFramework;
using HslCommunication.LogNet;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace HslCommunication.Enthernet
{
	/// <summary>
	/// 一个支持完全自定义的Http服务器，支持返回任意的数据信息，方便调试信息，详细的案例请查看API文档信息<br />
	/// A Http server that supports fully customized, supports returning arbitrary data information, which is convenient for debugging information. For detailed cases, please refer to the API documentation information
	/// </summary>
	/// <example>
	/// 我们先来看看一个最简单的例子，如何进行实例化的操作。
	/// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\Enthernet\HttpServerSample.cs" region="Sample1" title="基本的实例化" />
	/// 通常来说，基本的实例化，返回固定的数据并不能满足我们的需求，我们需要返回自定义的数据，有一个委托，我们需要自己指定方法.
	/// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\Enthernet\HttpServerSample.cs" region="Sample2" title="自定义返回" />
	/// 我们实际的需求可能会更加的复杂，不同的网址会返回不同的数据，所以接下来我们需要对网址信息进行判断。
	/// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\Enthernet\HttpServerSample.cs" region="Sample3" title="区分网址" />
	/// 如果我们想增加安全性的验证功能，比如我们的api接口需要增加用户名和密码的功能，那么我们也可以实现
	/// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\Enthernet\HttpServerSample.cs" region="Sample4" title="安全实现" />
	/// 当然了，如果我们想反回一个完整的html网页，也是可以实现的，甚至添加一些js的脚本，下面的例子就简单的说明了如何操作
	/// <code lang="cs" source="HslCommunication_Net45.Test\Documentation\Samples\Enthernet\HttpServerSample.cs" region="Sample5" title="返回html" />
	/// 如果需要实现跨域的操作，可以将属性<see cref="P:HslCommunication.Enthernet.HttpServer.IsCrossDomain" /> 设置为<c>True</c>
	/// </example>
	public class HttpServer
	{
		private int receiveBufferSize = 2048;

		private int port = 80;

		private HttpListener listener;

		private ILogNet logNet;

		private Encoding encoding = Encoding.UTF8;

		private Func<HttpListenerRequest, HttpListenerResponse, string, string> handleRequestFunc;

		/// <inheritdoc cref="P:HslCommunication.Core.Net.NetworkBase.LogNet" />
		public ILogNet LogNet
		{
			get
			{
				return logNet;
			}
			set
			{
				logNet = value;
			}
		}

		/// <summary>
		/// 获取或设置当前服务器的编码信息，默认为UTF8编码<br />
		/// Get or set the encoding information of the current server, the default is UTF8 encoding
		/// </summary>
		public Encoding ServerEncoding
		{
			get
			{
				return encoding;
			}
			set
			{
				encoding = value;
			}
		}

		/// <summary>
		/// 获取或设置是否支持跨域操作<br />
		/// Get or set whether to support cross-domain operations
		/// </summary>
		public bool IsCrossDomain
		{
			get;
			set;
		}

		/// <summary>
		/// 获取或设置当前的自定义的处理信息，如果不想继承实现方法，可以使用本属性来关联你自定义的方法。<br />
		/// Get or set the current custom processing information. If you don't want to inherit the implementation method, you can use this attribute to associate your custom method.
		/// </summary>
		public Func<HttpListenerRequest, HttpListenerResponse, string, string> HandleRequestFunc
		{
			get
			{
				return handleRequestFunc;
			}
			set
			{
				handleRequestFunc = value;
			}
		}

		/// <summary>
		/// 获取当前的端口号信息<br />
		/// Get current port number information
		/// </summary>
		public int Port => port;

		/// <summary>
		/// 实例化一个默认的对象，当前的运行，需要使用管理员的模式运行<br />
		/// Instantiate a default object, the current operation, you need to use the administrator mode to run
		/// </summary>
		public HttpServer()
		{
		}

		/// <summary>
		/// 启动服务器，正常调用该方法时，应该使用try...catch...来捕获错误信息<br />
		/// Start the server and use try...catch... to capture the error message when calling this method normally
		/// </summary>
		/// <param name="port">端口号信息</param>
		/// <exception cref="T:System.Net.HttpListenerException"></exception>
		/// <exception cref="T:System.ObjectDisposedException"></exception>
		public void Start(int port)
		{
			this.port = port;
			listener = new HttpListener();
			listener.Prefixes.Add($"http://+:{port}/");
			listener.Start();
			listener.BeginGetContext(GetConnectCallBack, listener);
			logNet?.WriteDebug(ToString() + " Server Started, wait for connections");
		}

		/// <summary>
		/// 关闭服务器<br />
		/// Shut down the server
		/// </summary>
		public void Close()
		{
			listener?.Close();
		}

		private async void GetConnectCallBack(IAsyncResult ar)
		{
			object asyncState = ar.AsyncState;
			HttpListener listener = asyncState as HttpListener;
			if (listener == null)
			{
				return;
			}
			HttpListenerContext context = null;
			try
			{
				context = listener.EndGetContext(ar);
			}
			catch (Exception ex4)
			{
				Exception ex = ex4;
				logNet?.WriteException(ToString(), ex);
			}
			int restartcount = 0;
			while (true)
			{
				try
				{
					listener.BeginGetContext(GetConnectCallBack, listener);
				}
				catch (Exception ex4)
				{
					Exception ex3 = ex4;
					logNet?.WriteException(ToString(), ex3);
					restartcount++;
					if (restartcount >= 3)
					{
						logNet?.WriteError(ToString() + " ReGet Content Failed!");
						return;
					}
					Thread.Sleep(1000);
					continue;
				}
				break;
			}
			if (context == null)
			{
				return;
			}
			HttpListenerRequest request = context.Request;
			HttpListenerResponse response = context.Response;
			if (response != null)
			{
				try
				{
					if (IsCrossDomain)
					{
						context.Response.AppendHeader("Access-Control-Allow-Origin", request.Headers["Origin"]);
						context.Response.AppendHeader("Access-Control-Allow-Headers", "*");
						context.Response.AppendHeader("Access-Control-Allow-Method", "POST,GET,PUT,OPTIONS,DELETE");
						context.Response.AppendHeader("Access-Control-Allow-Credentials", "true");
						context.Response.AppendHeader("Access-Control-Max-Age", "3600");
					}
					context.Response.AddHeader("Content-type", "Content-Type: text/html; charset=utf-8");
				}
				catch (Exception ex4)
				{
					Exception ex2 = ex4;
					logNet?.WriteError(ToString(), ex2.Message);
				}
			}
			string data = await GetDataFromRequestAsync(request);
			response.StatusCode = 200;
			try
			{
				string ret = HandleRequest(request, response, data);
				using (Stream stream = response.OutputStream)
				{
					if (string.IsNullOrEmpty(ret))
					{
						await stream.WriteAsync(new byte[0], 0, 0);
					}
					else
					{
						byte[] buffer = encoding.GetBytes(ret);
						await stream.WriteAsync(buffer, 0, buffer.Length);
					}
				}
				logNet?.WriteDebug(ToString() + " New Request [" + request.HttpMethod + "], " + request.RawUrl);
			}
			catch (Exception ex4)
			{
				logNet?.WriteException(ex: ex4, keyWord: ToString() + " Handle Request[" + request.HttpMethod + "], " + request.RawUrl);
			}
		}

		private string GetDataFromRequest(HttpListenerRequest request)
		{
			try
			{
				List<byte> list = new List<byte>();
				byte[] array = new byte[receiveBufferSize];
				int num = 0;
				int num2 = 0;
				do
				{
					num = request.InputStream.Read(array, 0, array.Length);
					num2 += num;
					list.AddRange(SoftBasic.ArraySelectBegin(array, num));
				}
				while (num != 0);
				return encoding.GetString(list.ToArray(), 0, num2);
			}
			catch
			{
				return string.Empty;
			}
		}

		private async Task<string> GetDataFromRequestAsync(HttpListenerRequest request)
		{
			try
			{
				List<byte> byteList = new List<byte>();
				byte[] byteArr = new byte[receiveBufferSize];
				int len = 0;
				int readLen;
				do
				{
					readLen = await request.InputStream.ReadAsync(byteArr, 0, byteArr.Length);
					len += readLen;
					byteList.AddRange(SoftBasic.ArraySelectBegin(byteArr, readLen));
				}
				while (readLen != 0);
				return encoding.GetString(byteList.ToArray(), 0, len);
			}
			catch
			{
				return string.Empty;
			}
		}

		/// <summary>
		/// 根据客户端的请求进行处理的核心方法，可以返回自定义的数据内容，只需要集成重写即可。<br />
		/// The core method of processing according to the client's request can return custom data content, and only needs to be integrated and rewritten.
		/// </summary>
		/// <param name="request">请求</param>
		/// <param name="response">回应</param>
		/// <param name="data">Body数据</param>
		/// <returns>返回的内容</returns>
		protected virtual string HandleRequest(HttpListenerRequest request, HttpListenerResponse response, string data)
		{
			if (HandleRequestFunc != null)
			{
				return HandleRequestFunc(request, response, data);
			}
			return "This is HslWebServer, Thank you for use!";
		}

		/// <inheritdoc />
		public override string ToString()
		{
			return $"HttpServer[{port}]";
		}

		/// <summary>
		/// 使用指定的对象来返回网络的API接口，前提是传入的数据为json参数，返回的数据为json数据，详细参照说明<br />
		/// Use the specified object to return the API interface of the network, 
		/// provided that the incoming data is json parameters and the returned data is json data, 
		/// please refer to the description for details
		/// </summary>
		/// <param name="request">当前的请求信息</param>
		/// <param name="json">json格式的参数信息</param>
		/// <param name="obj">等待解析的api解析的对象</param>
		/// <returns>等待返回客户的结果</returns>
		public static string HandleObjectMethod(HttpListenerRequest request, string json, object obj)
		{
			MethodInfo method;
			object[] array;
			try
			{
				JObject val = JObject.Parse(json);
				string rawUrl = request.RawUrl;
				string name = (rawUrl.LastIndexOf('/') >= 0) ? rawUrl.Substring(rawUrl.LastIndexOf('/') + 1) : rawUrl;
				method = obj.GetType().GetMethod(name);
				if (method == null)
				{
					return new OperateResult("Current Api ：" + rawUrl + " not exsist").ToJsonString((Formatting)1);
				}
				if (request.HttpMethod == "POST")
				{
					object[] customAttributes = method.GetCustomAttributes(typeof(HttpPost), inherit: false);
					if (customAttributes == null || customAttributes.Length == 0)
					{
						return new OperateResult("Current Api ：" + rawUrl + " not support POST").ToJsonString((Formatting)1);
					}
				}
				else if (request.HttpMethod == "GET")
				{
					object[] customAttributes2 = method.GetCustomAttributes(typeof(HttpGet), inherit: false);
					if (customAttributes2 == null || customAttributes2.Length == 0)
					{
						return new OperateResult("Current Api ：" + rawUrl + " not support GET").ToJsonString((Formatting)1);
					}
				}
				ParameterInfo[] parameters = method.GetParameters();
				array = new object[parameters.Length];
				for (int i = 0; i < parameters.Length; i++)
				{
					if (parameters[i].ParameterType == typeof(byte))
					{
						array[i] = ((JToken)val).Value<byte>((object)parameters[i].Name);
						continue;
					}
					if (parameters[i].ParameterType == typeof(short))
					{
						array[i] = ((JToken)val).Value<short>((object)parameters[i].Name);
						continue;
					}
					if (parameters[i].ParameterType == typeof(ushort))
					{
						array[i] = ((JToken)val).Value<ushort>((object)parameters[i].Name);
						continue;
					}
					if (parameters[i].ParameterType == typeof(int))
					{
						array[i] = ((JToken)val).Value<int>((object)parameters[i].Name);
						continue;
					}
					if (parameters[i].ParameterType == typeof(uint))
					{
						array[i] = ((JToken)val).Value<uint>((object)parameters[i].Name);
						continue;
					}
					if (parameters[i].ParameterType == typeof(long))
					{
						array[i] = ((JToken)val).Value<long>((object)parameters[i].Name);
						continue;
					}
					if (parameters[i].ParameterType == typeof(ulong))
					{
						array[i] = ((JToken)val).Value<ulong>((object)parameters[i].Name);
						continue;
					}
					if (parameters[i].ParameterType == typeof(double))
					{
						array[i] = ((JToken)val).Value<double>((object)parameters[i].Name);
						continue;
					}
					if (parameters[i].ParameterType == typeof(float))
					{
						array[i] = ((JToken)val).Value<float>((object)parameters[i].Name);
						continue;
					}
					if (parameters[i].ParameterType == typeof(bool))
					{
						array[i] = ((JToken)val).Value<bool>((object)parameters[i].Name);
						continue;
					}
					if (parameters[i].ParameterType == typeof(string))
					{
						array[i] = ((JToken)val).Value<string>((object)parameters[i].Name);
						continue;
					}
					if (parameters[i].ParameterType == typeof(DateTime))
					{
						array[i] = ((JToken)val).Value<DateTime>((object)parameters[i].Name);
						continue;
					}
					if (parameters[i].ParameterType == typeof(byte[]))
					{
						array[i] = (from m in ((IEnumerable<JToken>)val.GetValue(parameters[i].Name)).ToArray()
							select Extensions.Value<byte>((IEnumerable<JToken>)m)).ToArray();
						continue;
					}
					if (parameters[i].ParameterType == typeof(short[]))
					{
						array[i] = (from m in ((IEnumerable<JToken>)val.GetValue(parameters[i].Name)).ToArray()
							select Extensions.Value<short>((IEnumerable<JToken>)m)).ToArray();
						continue;
					}
					if (parameters[i].ParameterType == typeof(ushort[]))
					{
						array[i] = (from m in ((IEnumerable<JToken>)val.GetValue(parameters[i].Name)).ToArray()
							select Extensions.Value<ushort>((IEnumerable<JToken>)m)).ToArray();
						continue;
					}
					if (parameters[i].ParameterType == typeof(int[]))
					{
						array[i] = (from m in ((IEnumerable<JToken>)val.GetValue(parameters[i].Name)).ToArray()
							select Extensions.Value<int>((IEnumerable<JToken>)m)).ToArray();
						continue;
					}
					if (parameters[i].ParameterType == typeof(uint[]))
					{
						array[i] = (from m in ((IEnumerable<JToken>)val.GetValue(parameters[i].Name)).ToArray()
							select Extensions.Value<uint>((IEnumerable<JToken>)m)).ToArray();
						continue;
					}
					if (parameters[i].ParameterType == typeof(long[]))
					{
						array[i] = (from m in ((IEnumerable<JToken>)val.GetValue(parameters[i].Name)).ToArray()
							select Extensions.Value<long>((IEnumerable<JToken>)m)).ToArray();
						continue;
					}
					if (parameters[i].ParameterType == typeof(ulong[]))
					{
						array[i] = (from m in ((IEnumerable<JToken>)val.GetValue(parameters[i].Name)).ToArray()
							select Extensions.Value<ulong>((IEnumerable<JToken>)m)).ToArray();
						continue;
					}
					if (parameters[i].ParameterType == typeof(float[]))
					{
						array[i] = (from m in ((IEnumerable<JToken>)val.GetValue(parameters[i].Name)).ToArray()
							select Extensions.Value<float>((IEnumerable<JToken>)m)).ToArray();
						continue;
					}
					if (parameters[i].ParameterType == typeof(double[]))
					{
						array[i] = (from m in ((IEnumerable<JToken>)val.GetValue(parameters[i].Name)).ToArray()
							select Extensions.Value<double>((IEnumerable<JToken>)m)).ToArray();
						continue;
					}
					if (parameters[i].ParameterType == typeof(bool[]))
					{
						array[i] = (from m in ((IEnumerable<JToken>)val.GetValue(parameters[i].Name)).ToArray()
							select Extensions.Value<bool>((IEnumerable<JToken>)m)).ToArray();
						continue;
					}
					if (parameters[i].ParameterType == typeof(string[]))
					{
						array[i] = (from m in ((IEnumerable<JToken>)val.GetValue(parameters[i].Name)).ToArray()
							select Extensions.Value<string>((IEnumerable<JToken>)m)).ToArray();
						continue;
					}
					if (parameters[i].ParameterType == typeof(DateTime[]))
					{
						array[i] = (from m in ((IEnumerable<JToken>)val.GetValue(parameters[i].Name)).ToArray()
							select Extensions.Value<DateTime>((IEnumerable<JToken>)m)).ToArray();
						continue;
					}
					throw new Exception($"Can't support parameter [{parameters[i].Name}] type : {parameters[i].ParameterType}");
				}
			}
			catch (Exception ex)
			{
				return new OperateResult("Current Api ：" + request.RawUrl + " Wrong，Reason：" + ex.Message).ToJsonString((Formatting)1);
			}
			return method.Invoke(obj, array).ToJsonString((Formatting)1);
		}
	}
}
